/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2010 huliqing, huliqing.cn@gmail.com
 *
 * This file is part of QFaces.
 * QFaces is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * QFaces is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with QFaces.  If not, see <http://www.gnu.org/licenses/>.
 *
 * 这个文件是QFaces的一部分。
 * 您可以单独使用或分发这个文件，但请不要移除这个头部声明信息.
 * QFaces是一个自由软件，您可以自由分发、修改其中的源代码或者重新发布它，
 * 新的任何修改后的重新发布版必须同样在遵守LGPL3或更后续的版本协议下发布.
 * 关于LGPL协议的细则请参考COPYING、COPYING.LESSER文件，
 * 您可以在QFaces的相关目录中获得LGPL协议的副本，
 * 如果没有找到，请连接到 http://www.gnu.org/licenses/ 查看并获取副本。
 *
 * - Author: Huliqing
 * - Contact: huliqing.cn@gmail.com
 * - License: GNU Lesser General Public License (LGPL)
 * - Blog and source code availability: http://www.huliqing.name/
 */


package name.huliqing.qfaces.component;

import name.huliqing.qfaces.model.Item;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.el.MethodExpression;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UISelectItem;
import javax.faces.component.UISelectItems;
import javax.faces.component.UISelectOne;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.model.SelectItem;
import javax.xml.parsers.ParserConfigurationException;
import name.huliqing.qfaces.QFaces;
import name.huliqing.qfaces.XmlOper;
import name.huliqing.qfaces.model.Items;
import name.huliqing.qfaces.model.Level;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
 
/**
 *
 * @author huliqing
 */
public class Linkage extends UIComponentBase implements Ajax {
	private final static Logger logger = Logger.getLogger(Linkage.class.getName());
	// 所有的可见参数
	private MethodExpression listener = null;	// 所绑定的用于获取选项列表的侦听器
	private String _for = null;					// 对应的selectOne组件,由“，”号分隔符分开，对应组件的short id
	 
	// flag
	private Boolean isPostBack = false;			// 是否是回传请求，只有在提交时才true
	private Boolean isDefaultValue = false;		// 是否有默认值，在用户修改操作时会有默认值,(以0Level有值为准)
	
	/**
	 * 当用户在前端改变选项列表之后，最终在提交时需要将各自对应的下拉选项列表
	 * 一起提交到后台，并重新设置对应的selectOne组件的child列表（可选项），否
	 * 则前台与后台的child选择不一致，从而会导致后台不能正确设置该selectOne组
	 * 件的值.
	 */
	private String[] hiddenOptions = null;

    public void setListener(MethodExpression listener) {
        this.listener = listener;
    }

    @Override
    public Object saveState(FacesContext fc) {
        Object[] objs = new Object[3];
        objs[0] = super.saveState(fc);
        objs[1] = this.listener;
        objs[2] = this.hiddenOptions;
        return objs;
    }

    @Override
    public void restoreState(FacesContext fc, Object state) {
        Object[] objs = (Object[]) state;
        super.restoreState(fc, objs[0]);
        this.listener = (MethodExpression) objs[1];
        this.hiddenOptions = (String[]) objs[2];
    }

	@Override
    public void decode(FacesContext fc) {
        this.isPostBack = true; // 标志为postBack,这个参数在传递到客户端时很有用
        String _for = (String) this.getAttributes().get("for");
        String[] targetShortIds = null;
        if (_for != null) {
            targetShortIds = _for.split(",");
        }
        if (targetShortIds != null) {
            Map<String, String> params = fc.getExternalContext().getRequestParameterMap();
            if (this.hiddenOptions == null) {
                this.hiddenOptions = new String[targetShortIds.length];
            }
            // 跳过了第一个for所指定的select,这是因为，第一个select的下拉选项是不变的，并且在
            // 第一时间就提供的，同时客户端hiddenValue域下没有保存第一级的下拉选项数据
            for (int i = 1; i < targetShortIds.length; i++) {
                String shortId = targetShortIds[i].trim();
                UIComponent temp = this.findComponent(shortId);
                if (temp instanceof UISelectOne) {
                    UISelectOne select = (UISelectOne) temp;
                    String targetId = select.getClientId(fc);
                    String hiddenId = targetId + ":hiddenValue";
                    String hiddenValue = params.get(hiddenId);
                    this.hiddenOptions[i] = hiddenValue;    // 保存用户选择后的列表,这在回传请求时起作用。
                    // 清除select已经有的下拉子选项，并准备重置子选ad
                    List<UIComponent> childs = select.getChildren();
                    if (childs != null && !childs.isEmpty()) {
                    	Iterator<UIComponent> it = childs.iterator();
                        while (it.hasNext()) {
                            UIComponent childUI = it.next();
                            if ((childUI instanceof UISelectItem) || 
                                    (childUI instanceof UISelectItems)) {
                                it.remove();
                            }
                        }
                        
                    }
                    // 在hiddenValue为null,或""的情况下，可能是因为用户第二次提交时没有作任何选择，
                    // 因而，前台js没有设置hiddenValue的值,只有select onchange后才会触发hiddenValue
                    // 改变，因而才需要重设select组件的child值。
                    if (hiddenValue != null && !"".equals(hiddenValue)) {
                    	List<SelectItem> items = parseOptions(hiddenValue);
                        UISelectItems uiItems = new UISelectItems();
                        uiItems.setValue(items);
                        childs.add(uiItems);
                    } 
                }
            }
        }
    }
    
    /**
     * <items>
     *     <item value="" label="" selected="true/false"/>
     *     <item value="" label="" selected="true/false"/>
     * </items>
     * @param hiddenValue
     * @return options
     */
    private List<SelectItem> parseOptions(String hiddenValue) {
        List<SelectItem> items = new ArrayList<SelectItem>();
        Document doc = null;
        try {
            doc = XmlOper.newDocument(hiddenValue);
        } catch (Exception e) {
        	logger.warning(e.getMessage());
            return items;
        }
        Element root = doc.getDocumentElement();
        NodeList nodes = root.getElementsByTagName("item");
        if (nodes != null) {
            int len = nodes.getLength();
            for (int i = 0; i < len; i++) {
                Element ele = (Element) nodes.item(i);
                String value = ele.getAttribute("value");
                String label = ele.getAttribute("label");
                // selected属性取消
                // String selected = ele.getAttribute("selected");
                SelectItem item = new SelectItem(value, label);
                items.add(item);
            }
        }
        return items;
    }

    @Override
    public void encodeBegin(FacesContext fc) throws IOException {
        QFaces.getResLoader2().loadJavaScript(this, "Linkage.js");

        this._for = (String) this.getAttributes().get("for");
        
        // 1.获取for所指定的组件的id,并处理成长id,然后通过","号分隔符将它们组件起来
        // 再发送到客户端.
        // 2.为每一个对应的目标组件创建相应的hidden组件，用于保存在用户临提交前的下拉选
        // 项列表。这些hidden会在回传时一并提交，并最终分析后重设为相对应selectOne组件的
        // 子选项（selectItem),否则在回传后相应的selectOne会认为自己没有子选项而不能正确
        // 设置自身的值
        String[] targetShortIds = null;
        StringBuilder sb = new StringBuilder();
        if (this._for != null) {
            targetShortIds = this._for.split(",");
        }
        if (targetShortIds != null) {
            int current = 0;
            for (String shortId : targetShortIds) {
                UIComponent temp = this.findComponent(shortId.trim());
                if (temp instanceof UISelectOne) {
                	UISelectOne select = (UISelectOne) temp;
                	// 当第一个select有值、并且不为空时，则认为有默认值，换句话说，用户可能正在进行修改操作
                	if (current == 0 && select.getValue() != null && !"".equals(select.getValue())) {
                		this.isDefaultValue = true;
                	}
                    sb.append(select.getClientId(fc) + ",");
                    // 保存选项列表
                    encodeHiddenOptions(fc, 
                    		select.getClientId(fc) + ":hiddenValue", 
                    		(this.hiddenOptions != null ? this.hiddenOptions[current] : ""));
                    // 输出select控件的默认值，当用户设置了select的默认值的时候，这很重要。
                    encodeHiddenOptions(fc, 
                    		select.getClientId(fc) + ":defaultValue", 
                    		(select.getValue() != null ? select.getValue().toString() : ""));
                    current++;
                } else {
                	logger.severe("The component with id:" + temp.getClientId(fc) + " was not an UISelectOne component!");
                }
            }
        }
        // 处理掉可能存在的最后的“，”号
        String targetIds = sb.toString();
        if (targetIds != null && targetIds.endsWith(",")) {
            targetIds = targetIds.substring(0, targetIds.length() - 1);
        }
        // 用于触发Ajax调用
        setAjaxInvoke(fc, targetIds);
    }
    
    /**
     * 用于保存隐藏的选项列表.
     * @param fc
     * @param hiddenId
     * @param current
     * @throws java.io.IOException
     */
    private void encodeHiddenOptions(FacesContext fc, String hiddenId, String value) throws IOException {
        ResponseWriter rw = fc.getResponseWriter();
        rw.startElement("input", this);
        rw.writeAttribute("type", "hidden", null);
        rw.writeAttribute("id", hiddenId, null);
        rw.writeAttribute("name", hiddenId, null);
        rw.writeAttribute("value", value, null);
        rw.endElement("input");
    }

    private void setAjaxInvoke(FacesContext fc, String targetIds) throws IOException {
    	logger.fine("setAjaxInvoke...");
        if (this.listener == null) {
        	logger.severe("Attribute of \"listener\" not found!");
            return;
        }
        String clientId = this.getClientId(fc);
        String componentClass = this.getClass().getName();
        String listenerExp = this.listener.getExpressionString();

        StringBuilder js_init = new StringBuilder("Q.F.UILinkage.init(")
        .append(" \"" + clientId + "\"")
        .append(",\"" + componentClass + "\"")
        .append(",\"" + targetIds + "\"")
        .append(",\"" + listenerExp + "\"")
        .append(",\"" + this.isPostBack.toString() + "\"")
        .append(",\"" + this.isDefaultValue.toString() + "\"")
        .append(")");
        
        QFaces.getResLoader2().loadJavaScriptInit(this, js_init.toString());
    }

    /**
     * return value:
     * <items>
     *      <item value="" label="" selected="true/false"/>
     *      <item value="" label="" selected="true/false"/>
     * </items>
     * @param params
     * @param fc
     */
    public void ajax(Map<String, String> params, FacesContext fc) {
    	logger.fine("UILinkage.ajaxInvokeOnGet...");
        String listenerExp = params.get("listenerExp");
        
        int levelCount = Integer.parseInt(params.get("levelCount"));
        Level lastLevel = null;
        for (int i = 0; i < levelCount; i++) {
        	String value = params.get("level_" + i);
        	Level level = new Level(i, value, value, lastLevel);
        	lastLevel = level;
        }

        if (listenerExp == null) {
            return;
        }
        MethodExpression me = QFaces.createMethodExpression(listenerExp, List.class, new Class[]{Level.class});
        Items results = (Items) me.invoke(fc.getELContext(), new Object[]{lastLevel});
        if (results != null) {
            response(fc, results.getValue());
        }
    }
    
//   /**
//    * @version jsf1.1
//    */
//   public void ajaxInvokeOnGet(Map<String, String> params, FacesContext fc) {
//       Logger.fine("UILinkage.ajaxInvokeOnGet...");
//       String listenerExp = params.get("listenerExp");
//       String levelTemp = params.get("level");
//       String value = params.get("value");
//       String label = params.get("label");
//
//       if (listenerExp == null) {
//           return;
//       }
//       Level level = new Level(Integer.parseInt(levelTemp), value, label);
//       MethodBinding me = QFaces.createMethodBinding(listenerExp, new Class[]{Level.class});
//       Items results = (Items) me.invoke(fc, new Object[]{level});
//       if (results != null) {
//           response(fc, results.getValue());
//       }
//   }

    public void response(FacesContext fc, List<Item> results) {
    	logger.fine("UILinkage.response...");
        try {
            Document doc = XmlOper.newDocument();
            Element items = doc.createElement("items");
            doc.appendChild(items);
            for (Item it : results) {
                Element item = doc.createElement("item");
                item.setAttribute("value", it.getValue());
                item.setAttribute("label", it.getLabel());
                // 该selected属性已经取消
//                item.setAttribute("selected", String.valueOf(it.isSelected()));
                items.appendChild(item);
            }
            QFaces.getResponse().responseXML(fc, doc);
        } catch (ParserConfigurationException e) {
        	logger.warning("ParserConfigurationException:" + e.getMessage());
        }
    }

	@Override
	public String getFamily() {
		// TODO Auto-generated method stub
		return getClass().getName();
	}

}
